﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SqlTypes;
using System.Data.SqlClient;
using System.Data;
using DTO=Microsoft.MSE.Catalog.Contracts;
using System.Xml;
using System.Runtime.Serialization;
using System.IO;

namespace Microsoft.MSE.Catalog.Module.Data
{
    public class RepositoryChange
    {
        public static DTO.DBEventContext GetRepositoryChange(TimeSpan messageExpiration, out bool messagesExist, SqlTransaction tran)
        {
            SqlXml message = null;
            DateTime messageTime = DateTime.MinValue;
            messagesExist = false;
            using (SqlCommand cmd = new SqlCommand("[ServiceEngine].[DeQueueRepositoryChange]", tran.Connection, tran))
            {
                cmd.CommandType = CommandType.StoredProcedure;
                using (SqlDataReader rdr = cmd.ExecuteReader())
                {
                    if (rdr.HasRows)
                    {
                        rdr.Read();
                        message = rdr.GetSqlXml(1);
                        messageTime = rdr.GetDateTime(2);

                        //check if message is expired
                        if (messageTime.CompareTo(DateTime.Now.Subtract(messageExpiration)) < 0)
                        {
                            messagesExist = true;
                            TraceHelper.TraceInformation("Removing expired repository change record. Created[{0}] Message:[{1}]", messageTime, message.IsNull ? "(null)" : message.Value);
                            message = null;
                        }
                    }
                }
            }

            DTO.DBEventContext eventContext = null;
            if (message == null)
                return eventContext;

            messagesExist = true;

            if (!message.IsNull)
            {
                try
                {
                    XmlReader xrdr = message.CreateReader();
                    xrdr.ReadToDescendant("DBEventContext", SC.WSENamespaceURI);
                    string tmp = xrdr.ReadOuterXml();
                    DataContractSerializer dcs = new DataContractSerializer(typeof(DTO.DBEventContext), "DBEventContext", SC.WSENamespaceURI);
                    eventContext = dcs.ReadObject(XmlReader.Create(new StringReader(tmp))) as DTO.DBEventContext;
                }
                catch (System.Exception ex)
                {
                    TraceHelper.TraceError("Error parsing Repository Change message.  Message will be discarded. Message: [{0}] Error [{1}]", message.Value, CatalogSvcHelper.GetExceptionDetails(ex));
                }
            }
            return eventContext;
        }

        public static void Save(DTO.DBEvents eventType, DTO.DBChangeType changeType, List<DTO.DBEventDataItem> eventData, SqlTransaction txn, SqlConnection conn)
        {
            if (eventData == null || eventData.Count == 0)
                return;

            DTO.DBEventContext eventContext = new DTO.DBEventContext();
            eventContext.ChangeType = changeType;
            eventContext.EventData = eventData;
            eventContext.Event = eventType;

            Save(eventContext, txn, conn);

        }
        public static void Save(DTO.DBEventContext eventContext, SqlTransaction txn, SqlConnection conn)
        {
            if (eventContext == null || eventContext.EventData == null || eventContext.EventData.Count == 0)
                return;

            DataContractSerializer dcs = new DataContractSerializer(typeof(DTO.DBEventContext), "DBEventContext", SC.WSENamespaceURI);
            StringBuilder sb = new StringBuilder();
            using (XmlWriter writer = XmlWriter.Create(new StringWriter(sb)))
            {
                dcs.WriteObject(writer, eventContext);
                writer.Flush();
            }
            SaveRepositoryChange(sb.ToString(), txn, conn);
        }
        public static void Save(DTO.RepositoryChangeSet changeSet, SqlTransaction txn, SqlConnection conn)
        {
            if (changeSet == null)
                return;

            if (changeSet.ConsolidateChanges)
                ConsolidateChanges(changeSet);

            foreach (DTO.DBEventContext eventContext in changeSet.Items)
            {
                Save(eventContext, txn, conn);
            }
        }

        private static void ConsolidateChanges(DTO.RepositoryChangeSet changeSet)
        {
            //consolidation rules
            //OperationUpdated events can be consolidated into EndpointUpdated events
            DTO.DBEventContext epEvents = changeSet.Items.FirstOrDefault(item => item.Event == DTO.DBEvents.EndpointsUpdated);
            if (epEvents == null || epEvents.EventData == null || epEvents.EventData.Count == 0)
                return;
            DTO.DBEventContext opEvents = changeSet.Items.FirstOrDefault(item => item.Event == DTO.DBEvents.OperationsUpdated);
            if (opEvents == null || opEvents.EventData == null || opEvents.EventData.Count == 0)
                return;

            foreach (DTO.DBEventDataItem epItem in epEvents.EventData)
            {
                for (int i = opEvents.EventData.Count - 1; i >= 0; i--)
                {
                    //for same runtime and endpoint, remove operation level event
                    if (epItem.RuntimeServerId == opEvents.EventData[i].RuntimeServerId && epItem.EndpointId == opEvents.EventData[i].EndpointId)
                        opEvents.EventData.RemoveAt(i);
                }
            }
        }
        private static void SaveRepositoryChange(string xmlMessage, SqlTransaction txn, SqlConnection conn)
        {
            bool autoclose = false;
            const string sprocname = "[ServiceEngine].[QueueRepositoryChange]";

            try
            {
                if (conn == null) conn = new SqlConnection(Configurator.svcConfig.DBConnString);
                if (conn.State == ConnectionState.Closed)
                {
                    autoclose = true;
                    conn.Open();
                }

                using (SqlCommand cmd = new SqlCommand(sprocname, conn, txn))
                {
                    cmd.CommandType = CommandType.StoredProcedure;
                    SqlParameter MessageParam = new SqlParameter("@Message", SqlDbType.Xml);
                    MessageParam.Value = @xmlMessage;
                    cmd.Parameters.Add(MessageParam);

                    cmd.ExecuteNonQuery();
                }

            }
            finally
            {
                if (autoclose) { conn.Close(); }
            }
        }

    }
}
